/*
 *--------------------------------------------------------------------------
 *   Battlefield - A Realtime Network Multiplayer Game
 *   =======================================================
 *   Developed by Group D02 - 2009/2010 Semester 4 - CS2103
 *     Harry Nguyen Duy Hoang <nnduyhoang@yahoo.co.uk>
 *     Kent Chng Siang Rong <fivefootway@gmail.com>
 *     Lim Yong Peng <limpeng1986@gmail.com>
 *     Loh Xiankun <u0807185@nus.edu.sg>
 *   Instructed by
 *     Dr. Damith C.Rajapakse <damith@gmail.com>
 *   =======================================================
 *   $Id: BattlefieldServerImp.java 650 2010-07-30 06:48:15Z YongPeng $
 *   $LastChangedDate: 2010-07-30 06:48:15 +0000 (Fri, 30 Jul 2010) $
 *   $LastChangedBy: YongPeng $
 *--------------------------------------------------------------------------
 */
package battlefield;

import battlefield.entity.Game;
import battlefield.entity.Map;
import battlefield.entity.Player;
import battlefield.entity.Unit;
import battlefield.entity.UnitModel;
import battlefield.manager.AIManager;
import battlefield.manager.GameManager;
import battlefield.manager.MapManager;
import battlefield.manager.MissionManager;
import battlefield.manager.PlayerManager;
import battlefield.manager.UnitManager;
import battlefield.manager.UnitModelManager;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.List;

/**
 *
 * @author Harry Nguyen, Kent
 */
public class BattlefieldServerImp extends UnicastRemoteObject implements BattlefieldServerInterface {

    private GameManager gameManager;
    private PlayerManager playerManager;
    private MapManager mapManager;
    private UnitManager unitManager;
    private UnitModelManager unitModelManager;
    private AIManager aiManager;
    private MissionManager missionManager;

    /**
     * Constructs the BattlefieldServerImp object.
     * @throws RemoteException
     */
    public BattlefieldServerImp() throws RemoteException {
        gameManager = new GameManager();
        playerManager = new PlayerManager();
        mapManager = new MapManager();
        unitManager = new UnitManager();
        unitModelManager = new UnitModelManager();
        missionManager = new MissionManager();
    }

    /**
     * Returns a Player object object with specific playerId.
     * @param playerId id of the Player object
     * @return the Player object with the specific playerId and null if it is unable to find the Player object.
     * @throws RemoteException
     */
    public Player getPlayer(int playerId) throws RemoteException {
        try {
            return playerManager.get(playerId);
        } catch (Exception ex) {
            return null;
        }
    }

    /**
     * Returns the updated copy of the Game object with the specific gameId.
     * This gameId is obtain from the object Game object that is returned from
     * the method createGame().
     *
     * @param gameId
     * @return
     * @throws RemoteException
     */
    public Game getGame(int gameId) throws RemoteException {
        try {
            return gameManager.get(gameId);
        } catch (Exception ex) {
            throw new RemoteException(ex.getMessage(), ex);
            //return null;
        }
    }

    /**
     * Return a Game Object after creating it.
     *
     * @param playerId specific playerId that was generated for the user.
     * @return a newly create Game Object.
     * @throws RemoteException
     */
    public Game createGame(int playerId) throws RemoteException {
        try {
            return gameManager.createGame(playerManager.get(playerId));
        } catch (Exception ex) {
            throw new RemoteException(ex.getMessage(), ex);
        }
    }

    /**
     * Returns a List of Game object.
     * @return a list of Game objects or an empty list
     * @throws RemoteException
     */
    public List<Game> getGames() throws RemoteException {
        return gameManager.listGames();
    }

    /**
     * Allows a Player object to be set into the list of player under a specific Game object
     *
     * @param playerId id of a Player object that must be created beforehand
     * @param gameId id of a Game object that must be created beforehand
     * @throws RemoteException
     */
    public void joinGame(int playerId, int gameId) throws RemoteException {
        try {
            gameManager.joinGame(gameManager.get(gameId), playerManager.get(playerId));
        } catch (BattlefieldException ex) {
            throw new RemoteException(ex.getMessage(), ex);
        }
    }

    /**
     * Allows a Artificially created Player object, with the difficulty level
     * specify, to be set into the Game Object
     *
     * @param playerId id of the artificially created Player object
     * @param gameId id of the Game object that have to be created beforehand
     * @param difficultyLevel the difficulty level of the AI. Accepted values
     * are 0 for normal and 1 for hard
     * @throws RemoteException
     */
    public void computerJoinGame(int playerId, int gameId, int difficultyLevel) throws RemoteException {
        try {
            Game game = gameManager.get(gameId);
            Player player = playerManager.get(playerId);
            Player computer = playerManager.registerPlayer();
            if (difficultyLevel == 0) {
                computer.setName("Com_Normal");
            } else if (difficultyLevel == 1) {
                computer.setName("Com_Hard");
            }
            computer.setGold(game.getStartGold());
            gameManager.joinGame(game, computer);
            aiManager = new AIManager(difficultyLevel, game, computer, player, gameManager,
                    playerManager, mapManager, unitManager, unitModelManager);
            aiManager.start();
        } catch (BattlefieldException ex) {
            throw new RemoteException(ex.getMessage(), ex);
        }
    }

    /**
     * Sets the starting amount of gold, duration to next reload and amount per
     * reload of a game.
     *
     * @param gameId id of the Game.
     * @param goldReloadDuration duration to next reload
     * @param goldReloadAmt amount per reload
     * @param startGold starting amount of gold
     * @throws RemoteException
     */
    public void setGameSettings(int gameId, int goldReloadDuration, int goldReloadAmt, int startGold) throws RemoteException {
        try {
            Game game = gameManager.get(gameId);
            gameManager.setGameSettings(game, goldReloadDuration, goldReloadAmt, startGold);

            for (Player player : game.getPlayers()) {
                player.setGold(startGold);
            }
        } catch (BattlefieldException ex) {
            throw new RemoteException(ex.getMessage(), ex);
        }
    }
    /**
     * Returns a Game object of the game that a player is currently in.
     *
     * @param playerId id of the Player object that must be created beforehand.
     * @return Game object that a player is currently in or a exception if it is not found.
     * @throws RemoteException
     */
    public Game getCurrentGame(int playerId) throws RemoteException {
        try {
            return gameManager.getCurrentGame(playerManager.get(playerId));
        } catch (BattlefieldException ex) {
            throw new RemoteException(ex.getMessage(), ex);
        }
    }

    /**
     * Returns a String array of name of maps.
     * @return String array of name of maps
     * @throws RemoteException
     */
    public String[] getMapNames() throws RemoteException {
        return mapManager.getAvailableMaps();
    }

    /**
     * Returns a list of UnitModel object
     * @return list of UnitModel object
     * @throws RemoteException
     */
    public List<UnitModel> getUnitModels() throws RemoteException {
        return unitModelManager.getUnitModels();
    }

    /**
     * Creates a Map object inside the Player object.
     *
     * @param playerId id of the Player object that have to be created beforehand
     * @param name name of the Map object
     * @return the Map that was created inside the Player object
     * @throws RemoteException
     */
    public Map createMap(int playerId, String name) throws RemoteException {
        try {
            return mapManager.createMap(playerManager.get(playerId), name);
        } catch (BattlefieldException ex) {
            throw new RemoteException(ex.getMessage(), ex);
        }
    }

    /**
     * Ends the tactical phase of a player in a game
     *
     * @param playerId id of the Player object.
     * @throws RemoteException
     */
    public void endTacticalPhase(int playerId) throws RemoteException {
        try {
            gameManager.endTacticalPhase(playerManager.get(playerId));
        } catch (BattlefieldException ex) {
            throw new RemoteException(ex.getMessage(), ex);
        }
    }

    /**
     * Buys a Unit and place it on the Map of a specific Player. This method 
     * returns an exception if it is unable to purchase unit due to lack of
     * funds and invalid positioning of the unit on
     * the map.
     *
     * @param playerId id of the specific player
     * @param modelId id of the UnitModel to purchase
     * @param x  horizontal position of the unit to be placed on the map
     * @param y vertical position of the unit to be placed on the map
     * @throws RemoteException
     */
    public void buyAndPlaceUnitOnMap(int playerId, int modelId, int x, int y) throws RemoteException {
        try {
            Player player = playerManager.get(playerId);
            Map map = player.getMap();
            UnitModel model = unitModelManager.get(modelId);

            if (map == null) {
                throw new BattlefieldException("Map not set.");
            }

            if (!mapManager.isUnitPlaceableOnMap(map, model, x, y)) {
                throw new BattlefieldException("Unit cannot be placed at (" + x + "," + y + ").");
            }

            if (player.getGold() < model.getPrice()) {
                throw new BattlefieldException("Not enough fund.");
            }

            mapManager.placeUnitOnMap(map, unitManager.create(model), x, y);

            player.setGold(player.getGold() - model.getPrice());
        } catch (BattlefieldException ex) {
            throw new RemoteException(ex.getMessage(), ex);
        }
    }

    /**
     * Buys a Weapon and place it on the Map of a opponent Player. This method
     * returns an exception if it is unable to purchase unit due to lack of
     * funds and invalid positioning of the unit on
     * the map.
     *
     * @param playerId id of Player object.
     * @param opponentPlayerId id of opponent Player
     * @param modelId id of the UnitModel of the Bomb
     * @param x horizontal position to place the weapon on
     * @param y vertical position to place the weapon on
     * @return
     * @throws RemoteException
     */
    public boolean buyAndUseWeaponOnMap(int playerId, int opponentPlayerId, int modelId, int x, int y) throws RemoteException {
        try {
            Map map = playerManager.get(opponentPlayerId).getMap();
            Player player = playerManager.get(playerId);
            UnitModel model = unitModelManager.get(modelId);

            if (map == null) {
                throw new BattlefieldException("Map not set.");
            }

            if (player.getGold() < model.getPrice()) {
                throw new BattlefieldException("Not enough fund.");
            }

            boolean success = mapManager.useWeaponOnMap(map, unitManager.create(model), x, y);

            player.setGold(player.getGold() - model.getPrice());

            return success;
        } catch (BattlefieldException ex) {
            throw new RemoteException(ex.getMessage(), ex);
        }
    }

    /**
     * Sells a Unit that the Player placed on the Map. Can only be used when
     * still in the tactical phase of the game.
     *
     * @param playerId id of Player object
     * @param x horizontal position of the unit on the map
     * @param y vertical position of the unit on the map
     * @throws RemoteException
     */
    public void sellUnitOnMap(int playerId, int x, int y) throws RemoteException {
        try {
            Player player = playerManager.get(playerId);
            Map map = player.getMap();

            if (map == null) {
                throw new BattlefieldException("Map not set.");
            }

            if (!player.getMap().equals(map)) {
                throw new BattlefieldException("This map is not your map.");
            }

            Unit unit = mapManager.removeUnitOnMap(map, x, y);

            player.setGold(player.getGold() + ((int) (unit.getModel().getPrice() * 0.9)));
        } catch (BattlefieldException ex) {
            throw new RemoteException(ex.getMessage(), ex);
        }
    }

    /**
     * Returns a Unit Object with the specified horizontal and vertical position
     * @param playerId id of the Player object
     * @param x horizontal position of the unit
     * @param y vertical position of the unit
     * @throws RemoteException
     */
    public Unit getUnitOnMap(int playerId, int x, int y) throws RemoteException {
        try {
            Player player = playerManager.get(playerId);
            Map map = player.getMap();

            if (map == null) {
                throw new BattlefieldException("Map not set.");
            }

            if (!player.getMap().equals(map)) {
                throw new BattlefieldException("This map is not your map.");
            }

            return map.getUnitAt(x, y);
        } catch (BattlefieldException ex) {
            throw new RemoteException(ex.getMessage(), ex);
        }
    }

    /**
     * Sets the phase of the Player in a Game to war phase.
     * @param playerId
     * @throws RemoteException
     */
    public void beginWarPhase(int playerId) throws RemoteException {
        try {
            gameManager.startWarPhase(gameManager.getCurrentGame(playerManager.get(playerId)));
        } catch (BattlefieldException ex) {
            throw new RemoteException(ex.getMessage(), ex);
        }
    }

    /**
     * Increase the amount of gold of a Player
     *
     * @param playerId id of the Player
     * @param reloadQty amount of gold to increase
     * @throws RemoteException
     */
    public void topupGold(int playerId, int reloadQty) throws RemoteException {
        try {
            if(reloadQty<0)throw new BattlefieldException("Quantity must be at least 1");
            Player player = playerManager.get(playerId);
            player.setGold(player.getGold() + reloadQty);
        } catch (BattlefieldException ex) {
            throw new RemoteException(ex.getMessage(), ex);
        }

    }

    /**
     * Check if the player has any unit left
     *
     * @param playerId id of the Player to check
     * @return true is Player still have unit left, false is no more unit left
     * @throws RemoteException
     */
    public boolean isAlive(int playerId) throws RemoteException {
        try {
            return playerManager.isAlive(playerManager.get(playerId));
        } catch (BattlefieldException ex) {
            throw new RemoteException(ex.getMessage(), ex);
        }
    }

    /**
     * Updates the unit counts of the Player.
     *
     * @param playerId id of the Player object.
     * @throws RemoteException
     */
    public void updateUnitCounts(int playerId) throws RemoteException {
        try {
            playerManager.updateUnitCount(playerManager.get(playerId));
        } catch (BattlefieldException ex) {
            throw new RemoteException(ex.getMessage(), ex);
        }
    }

    /**
     * Register a new Player.
     * @return the Player object created.
     * @throws RemoteException
     */
    public Player registerPlayer() throws RemoteException {
        return playerManager.registerPlayer();
    }

    /**
     * Changes the name of the Player object.
     *
     * @param playerId id of Player object.
     * @param name the new name for the Player
     * @throws RemoteException
     */
    public void changePlayerName(int playerId, String name) throws RemoteException {
        try {
            playerManager.get(playerId).setName(name);
        } catch (BattlefieldException ex) {
            throw new RemoteException(ex.getMessage(), ex);
        }
    }

    /**
     * Check if a opponent joined a game that a Player is currently in.
     * @param playerId if of the Player currently in a game
     * @return
     * @throws RemoteException
     */
    public boolean isOpponentJoin(int playerId) throws RemoteException {
        try {
            return gameManager.getGamePlayers(gameManager.getCurrentGame(playerManager.get(playerId))).size() > 1;
        } catch (BattlefieldException ex) {
            throw new RemoteException(ex.getMessage(), ex);
        }
    }

    /**
     * Return the opponent Player object currently in game.
     *
     * @param playerId
     * @return Player object
     * @throws RemoteException
     */
    public Player getOpponent(int playerId) throws RemoteException {
        try {
            return gameManager.getOpponent(playerManager.get(playerId));
        } catch (Exception ex) {
            throw new RemoteException(ex.getMessage(), ex);
        }
    }

    /**
     * Check if the player is the host of the game.
     * @param playerId id of the Player
     * @return true if id belongs to the host
     * @throws RemoteException
     */
    public boolean isHost(int playerId) throws RemoteException {
        try {
            return gameManager.isGameHost(playerManager.get(playerId));
        } catch (Exception ex) {
            throw new RemoteException(ex.getMessage(), ex);
        }
    }

    /**
     * Create the Mission object of a player
     * @param playerId id of the Player
     * @throws RemoteException
     * @throws BattlefieldException
     */
    public void createMissions(int playerId) throws RemoteException, BattlefieldException {
        try {
            missionManager.createMissions(playerManager.get(playerId));
        } catch (Exception ex) {
            throw new RemoteException(ex.getMessage(), ex);
        }
    }

    /**
     * Returns the amount of gold earn from doing a mission
     * 
     * @param playerId id of the Player
     * @param missionName name of the mission
     * @return number of gold earned from doing a mission
     * @throws RemoteException
     */
    public int doMission(int playerId, String missionName) throws RemoteException {

        try {
            int gold = missionManager.doMission(missionName, playerManager.get(playerId));
            return gold;
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RemoteException(ex.getMessage(),ex);
            //return 0;
        }
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        unitModelManager.clear();
        unitManager.clear();
        mapManager.clear();
        playerManager.clear();
        gameManager.clear();
        aiManager = null;
    }
}
